Created by Ignasi 'Iggy' Bosch / @ignasibosch
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
$author = new Author();
// ... whatever
$validator = $this->get('validator');
$errors = $validator->validate($author);
class Author
{
/**
* @Assert\NotBlank()
*/
public $name;
}
public $validate = array(
'id' => array(
'rule' => 'blank',
'on' => 'create' <-------------
)
);
try {
$validator = ValidatorFactory::make(
'checkOutOrder',
Auth::role()
);
$order = $this->orderService
->setData($post)
->setValidator($validator)
->checkOut();
} catch (ValidationException $ex) {
return $this->returnBackWithErrors($ex);
}
input:valid { border-color: green; }
input:invalid { border-color: red; }
public function check($date, $numberOfSeats)
{
if ($date == ''){
throw new IllegalArgumentException("date is missing");
}
if ($this->isBefore($date)){
throw new IllegalArgumentException(
"date cannot be before today");
}
if ($numberOfSeats == ''){
throw new IllegalArgumentException(
"number of seats cannot be empty");
}
if ($numberOfSeats < 1){
throw new IllegalArgumentException(
"number of seats must be positive");
}
}
public function check($date, $numberOfSeats)
{
$errors = []
if ($date == ''){
$errors[] = "date is missing";
}
if ($this->isBefore($date)){
$errors[] = "date cannot be before today";
}
if ($numberOfSeats == ''){
$errors[] = "number of seats cannot be empty";
}
if ($numberOfSeats < 1){
$errors[] = "number of seats must be positive";
}
return $errors;
}
public function saveBooking($post)
{
$errors = $this->check($post['date'], $post['number_seats']);
if(!empty($errors)){
throw new FormValidationException($errors);
}
$booking = new Booking();
// ...
}
class Validator
{
protected $errors = [];
public function checkRequired($value, $field)
{
if (!trim($value)) {
$this->errors[$field] = 'Required value';
}
return $this;
}
public function checkAlphanumeric($value)
{
if ($value && !preg_match("/^[a-zA-Z0-9]+$/", $value)) {
$this->errors[$field] =
'The supplied value must be alphanumeric';
}
return $this;
}
public function isValid(){ return empty($this->errors);}
public function getErrors(){ return $this->errors;}
}
$validator = new Validator();
$validator->checkRequired($post['name'], 'name')
->checkAlphanumeric($post['name'], 'name');
if (!$validator->isValid()) {
return new JsonResponse($validator->getErrors(), 400);
}
/** Some action **/
class Required implements Rule
{
public function isValid($value){ /****/ }
public function getMessage(){ /****/ }
private function checkSomething($value){ /****/ }
}
class Email implements Rule
{
public function isValid($value){ /****/ }
public function getMessage(){ /****/ }
private function checkSomething($value){ /****/ }
}
class Alphanum implements Rule
{
public function isValid($value){ /****/ }
public function getMessage(){ /****/ }
private function checkSomething($value){ /****/ }
}
class UpdateEntryValidator extends AbstractValidator
implements Validator
{
public function __construct()
{
$this->rules = [
'name' => [ new Required(), new Alphanum() ],
'email' => [ new Required(), new Email() ]
];
}
}
abstract class AbstractValidator
{
protected $rules = [];
protected $errors = [];
public function validate($post)
{
foreach ($post as $field => $value) {
if(isset($this->rules[$field])){
$this->checkRules($field, $value);
}
}
}
protected function checkRules($field, $value)
{
foreach ($this->rules[$field] as $rule) {
if (!$rule->isValid($value)) {
$this->errors[$field] = $rule->getMessage();
}
}
}
public function isValid(){ return empty($this->errors); }
public function getErrors(){ return $this->errors; }
}
$action = Action::UPDATE;
$validator = EntryValidatorFactory::make($action);
$validator->validate($post);
if(!$validator->isValid()){
return new JsonResponse($validator->getErrors(), 400);
}
/** Some action **/
class EntryCreatedEvent extends Event implements EntryEvent
{
const NAME = 'entry.created';
protected $entry;
public function __construct(Entry $entry)
{
$this->entry = $entry;
}
public function getEntry()
{
return $this->entry;
}
}
class EntryValidationListener
{
private $entry;
private $validator;
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
public function onCreateAction(EntryEvent $event)
{
$this->validateEntry($event->getEntry());
}
private function validateEntry(Entry $entry)
{
$this->validator->setEntry($entry)->validate();
if(!$this->validator->isValid()){
throw new ValidationException(
$this->validator->getErrors());
}
}
}
$dispatcher = new EventDispatcher();
$validator = new CreateEntryValidator();
$listener = new EntryValidationListener($validator);
$dispatcher->addListener(EntryCreatedEvent::NAME,
array($listener, 'onCreateAction'));
$entry = new Entry();
// ... do something
$event = new EntryCreatedEvent($entry);
try {
$dispatcher->dispatch(EntryCreatedEvent::NAME, $event);
} catch (ValidationException $e) {
/** Return something to web **/
}
$entry = new Entry();
// ... do something
$event = new EntryCreatedEvent($entry);
try {
$dispatcher->dispatch(EntryCreatedEvent::NAME, $event);
} catch (ValidationException $e) {
/** Return something to CLI **/
}
class ShippedStatusValidator implements StatusValidatorInterface
{
private $status;
private $order
private $isShippedStatus = false;
public function __construct(Order $order)
{
$this->status = Status::SHIPPED;
$this->order = $order;
}
public function process()
{
$this->validateData()
->hasShipmentDate()
->hasNotDeliveryDate()
->isNotLaterStatus();
return $this->isShippedStatus ? $this->status : false;
}
protected function createdByAdmin(){ //... }
protected function validateData(){ //... }
protected function hasShipmentDate(){ //... }
protected function hasNotDeliveryDate(){ //... }
protected function isNotLaterStatus(){ //... }
}
class StandardOrderStatusProcessor extends AbstractStatusProcessor
implements StatusProcessorInterface
{
public function __construct(Order $order)
{
$validators = [
new ProcessingStatusValidator($order),
new ProcessAlertStatusValidator($order),
new PaidStatusValidator($order),
new CancelledStatusValidator($order),
new PaymentAlertStatusValidator($order),
new ExpiredStatusValidator($order),
new ReadyAlertStatusValidator($order),
new ShippedStatusValidator($order),
new ShipmentAlertStatusValidator($order),
new DeliveredStatusValidator($order),
new RejectedStatusValidator($order),
new CompleteStatusValidator($order)
];
$this->setValidators($validators);
}
}
abstract class AbstractStatusProcessor
{
private $validators = [];
public function setValidators(array $validators)
{
foreach ($validators as $validator) {
$this->addValidator($validator);
}
}
public function addValidator
(StatusValidatorInterface $validator)
{
$this->validators[] = $validator;
}
public function getStatus()
{
for ($i = 0, $status = FALSE;
$i < count($this->validators)
&& $status == FALSE;
$i++) {
$status = $this->validators[$i]->process();
}
return $status ? $status : $this->throwsStatusException();
}
}
public function isValidStatus(Order $order)
{
$statusProcessor = StatusProcessorFactory::make($order);
try {
return $order->getStatus() === $statusProcessor->getStatus();
} catch (StatusException $ex) {
return false;
}
}
class ValidateEntries extends Job
implements SelfHandling, ShouldQueue
{
use InteractsWithQueue, SerializesModels;
public function handle()
{
$entries = Entry::where('status', Entry::STATUS_COMPLETE)
->get();
foreach($entries as $entry){
$newStatus = $this->isValidEntry($entry)
? Entry::STATUS_VALID
: Entry::STATUS_ERROR
$this->updateEntry($entry, $newStatus);
}
}
private function isValidEntry(Entry $entry)
{ /****/ }
private function updateEntry(Entry $entry, $newStatus)
{ /****/ }
}
SourceMaking:
Chain of Responsibility
Martin
Fowler:
Replacing Throwing Exceptions with Notification
Martin Fowler:
ContextualValidation
DevShed:
Using Multiple Strategy Classes with the Strategy Design Pattern
Implementing
Fowler's Analysis Validator Pattern in Java
JSR 303: Bean Validation
Web Form Validation: Best Practices and Tutorials
Code Validation and Exception Handling: From the UI to the Backend
Symfony Validation
Laravel Validation
CakePHP Validation